package com.ruoyi.activity.service.impl;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.activity.mapper.ActivityMapper;
import com.ruoyi.activity.mapper.ApplyMapper;
import com.ruoyi.activity.mapper.ContestMapper;
import com.ruoyi.activity.service.IApplyService;
import com.ruoyi.activity.service.ITemplateService;
import com.ruoyi.common.constant.ActivitysConstant;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.BeanCopyUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.model.activity.Activity;
import com.ruoyi.model.apply.Apply;
import com.ruoyi.model.apply.dto.ApplyInfoDto;
import com.ruoyi.model.apply.vo.ApplyActivityVo;
import com.ruoyi.model.apply.vo.ApplyContestVo;
import com.ruoyi.model.auth.AuthContextHolder;
import com.ruoyi.model.contest.Contest;
import com.ruoyi.model.template.Template;
import com.ruoyi.model.template.TemplateValue;
import com.ruoyi.model.template.vo.TemplateVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author : 张怀秋
 **/
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class ApplyServiceImpl extends ServiceImpl<ApplyMapper, Apply> implements IApplyService {

    /**
     * 活动服务
     */
    private final ActivityMapper activityMapper;

    /**
     * 竞赛服务
     */
    private final ContestMapper contestMapper;

    /**
     * 模板服务
     */
    private final ITemplateService templateService;

    /**
     * 缓存服务
     */
    private final RedisCache redisCache;

    /**
     * 报名服务
     */
    private final ApplyMapper applyMapper;


    /**
     * 展示报名模板
     *
     * @param detailId 详情id
     * @param type     类型
     * @return AjaxResult
     */
    @Override
    public AjaxResult queryDetailApplicationTemplate(String detailId, String type) {
        String newType = Optional.ofNullable(type).orElseThrow(() ->
                new RuntimeException("传递的类型不能为空!"));
        String newDetailId = Optional.ofNullable(detailId).orElseThrow(() ->
                new RuntimeException("务必说明需要展示的详情Id!"));
        TemplateVo templateVo = new TemplateVo();
        //有些冗余的业务
        switch (newType) {
            case "0":
                Template templateActivity = templateService.getOne(Wrappers.<Template>lambdaQuery()
                        .eq(Template::getActivityId, newDetailId)
                        .eq(Template::getType, type));
                Contest result1 = contestMapper.selectById(newDetailId);
                if (templateActivity == null) {
                    return null;
                }
                templateVo = BeanCopyUtils.copyBean(templateActivity, TemplateVo.class);
                templateVo.setDetailName(result1.getTitle());
                break;
            case "1":
                Template templateContest = templateService.getOne(Wrappers.<Template>lambdaQuery()
                        .eq(Template::getActivityId, newDetailId)
                        .eq(Template::getType, type));
                if (templateContest == null) {
                    return null;
                }
                Activity result2 = activityMapper.selectById(newDetailId);
                templateVo = BeanCopyUtils.copyBean(templateContest, TemplateVo.class);
                templateVo.setDetailName(result2.getTitle());
                break;
            default:
                break;
        }
        //todo 推荐文章
        return AjaxResult.success(templateVo);
    }

    /**
     * 报名接口
     * <p>
     * 这里全部混在一起写是因为怕事务失效 造成严重的损失！
     *
     * @param applyInfoDto 报名信息
     * @return AjaxResult
     */
    @Override
    @SuppressWarnings("all")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult submitApply(ApplyInfoDto applyInfoDto) {
        Optional.ofNullable(applyInfoDto).map(ApplyInfoDto::getApplyStatus).ifPresent(status -> {
            //状态不为空证明逻辑正常 继续流程

            //1.判断当前报名的是否是竞赛 如果是竞赛 那么判断其中是否需要缴费 需要缴费要缴费成功后 才能报名
            if ("0".equals(applyInfoDto.getType()) && ActivitysConstant.APPLICATION_FINISH.equals(applyInfoDto.getApplyStatus())) {
                Contest contest = this.contestMapper.selectById(applyInfoDto.getActivityId());
                Optional.ofNullable(contest).map(Contest::getIsToll).filter("0"::equals).ifPresent(v -> {
                    //如果竞赛是收费的 那么执行到这一步应该已经要教了报名费用才对 在缴费接口中会根据userId保存缴费单号
                    // 如果传递的单号与redis中不一致不允许通过报名
                    String no = this.redisCache.getCacheObject("apply-" + AuthContextHolder.getUserId());
                    if (!applyInfoDto.getSingleNo().equals(no)) {
                        throw new RuntimeException("当前竞赛需要缴费才能继续报名!!!");
                    }
                });
            }

            //2.需要校验必填写的参数模板中是否有值为空值
            //如果用户没有使用该系统自带报名模板而是选择了线下报名的方式 那么这里不需要校验
            Optional.ofNullable(applyInfoDto.getIsUse()).filter(use -> use == 0).ifPresent(v -> {
                List<TemplateValue> templateValueList = applyInfoDto.getTemplateValueList()
                        .stream()
                        .filter(templateValue -> "0".equals(templateValue.getRequired())).collect(Collectors.toList());
                //进一步校验
                templateValueList.forEach(value -> {
                    if (StringUtils.isEmpty(value.getValue())) {
                        throw new RuntimeException("必填信息中有空值!");
                    }
                });
            });

            //3.校验当前的竞赛、活动是否已经满员 如果满员直接抛出异常
            //调用对应业务层或者Mapper的方法执行对应的操作
            //使用mysql行锁 (悲观锁) 去锁定当前记录
            Activity activity = null;
            Contest contest = null;
            Long maxPersonNum = 0L;
            if ("0".equals(applyInfoDto.getType())) {
                //当前是竞赛
                contest = this.contestMapper
                        .selectContestPersonNumByIdAndPessimisticLocks(applyInfoDto.getActivityId());
                maxPersonNum = contest.getPersonNum();
                Assert.isFalse(Objects.equals(maxPersonNum, contest.getParticipationNum())
                                || contest.getParticipationNum() > maxPersonNum
                        , "当前竞赛报名人数已满!");
            } else if ("1".equals(applyInfoDto.getType())) {
                //当前是活动
                activity = this.activityMapper
                        .selectActivityPersonNumByIdAndPessimisticLocks(applyInfoDto.getActivityId());
                maxPersonNum = activity.getPersonNum();
                Assert.isFalse(Objects.equals(maxPersonNum, activity.getParticipationNum())
                                || activity.getParticipationNum() > maxPersonNum
                        , "当前活动报名人数已满!");
            } else {
                throw new RuntimeException("抱歉,数据发生异常,请您稍后再试!");
            }

            //4.检索报名表中该用户是否已经完成报名
            Apply applyOne = this.baseMapper.selectOne(Wrappers.<Apply>lambdaQuery()
                    .eq(Apply::getUserId, AuthContextHolder.getUserId())
                    .eq(Apply::getActivityId, applyInfoDto.getActivityId())
                    .eq(Apply::getType, applyInfoDto.getType()));

            //获取到该信息判断状态是否为已经完成 如果已经完成那么直接抛出异常
            if (ObjectUtils.isNotEmpty(applyOne) && applyOne.getStatus() == 0) {
                throw new RuntimeException("您已经报名过了 不允许再次报名!");
            } else if (ObjectUtils.isNotEmpty(applyOne) && applyOne.getStatus() == 1) {
                //如果该信息判断不是已经完成 那么 走继续报名业务
                applyOne.setStatus(applyInfoDto.getApplyStatus()); //赋值新的报名状态
                applyOne.setContent(applyInfoDto.getIsUse() == 0 ? JSONObject.toJSONString(applyInfoDto.getTemplateValueList()) : null);
                //确认报名之后才保存时间如果只是等待报名展示可以赋值为空
                applyOne.setEnrollTime(
                        ActivitysConstant.APPLICATION_FINISH.equals(applyInfoDto.getApplyStatus()) ? new Date() : null
                );
                int resultNum = this.baseMapper.updateById(applyOne);
                Optional.of(resultNum).filter(num -> num != 0).orElseThrow(() -> new RuntimeException("报名失败 请稍等后尝试!"));
            } else {
                //5.保存报名信息进入报名表当中去 这是从来没有报名过的情况
                //将报名列表转化为json字段
                Apply apply = BeanCopyUtils.copyBean(applyInfoDto, Apply.class);
                String applyTemplateInfo = JSONObject.toJSONString(applyInfoDto.getTemplateValueList());
                apply.setContent(applyInfoDto.getIsUse() == 0 ? applyTemplateInfo : null);
                //确认报名之后才保存时间如果只是等待报名展示可以赋值为空
                apply.setEnrollTime(
                        ActivitysConstant.APPLICATION_FINISH.equals(applyInfoDto.getApplyStatus()) ? new Date() : null
                );
                apply.setUserId(AuthContextHolder.getUserId());
                apply.setStatus(applyInfoDto.getApplyStatus());
                //保存进入报名表
                int resultNum = this.baseMapper.insert(apply);
                Optional.of(resultNum).filter(num -> num != 0).orElseThrow(() -> new RuntimeException("报名失败 请稍等后尝试!"));
            }

            //6.更新已经被行锁的活动、竞赛详情的参赛人数
            Long quantityNum = 1L;
            if ("0".equals(applyInfoDto.getType())) {
                // 首先先去查看当前的竞赛是否是团队竞赛
                // 如果是团队赛 那么不管参赛竞赛是几个人 都需要直接新增已经参赛人数数量为最大团队赛人数
                // 例如某竞赛是团队赛 最大参赛人数是3个人 但是报名时候只有一个人填写了报名信息 他选择一个人参赛 那么他一个组成了3人团队！
                if ("0".equals(Objects.requireNonNull(contest).getIsTeam())) {
                    quantityNum = contest.getTeamNum();
                }
                //新增人数
                contest.setParticipationNum(contest.getParticipationNum() + quantityNum);
                //这里会使用乐观锁去操作数据库
                int row = contestMapper.updateById(contest);
                Optional.of(row).filter(r -> r == 1).orElseThrow(() -> new RuntimeException("当前在线报名人数较多,请稍后尝试!"));
            } else if ("1".equals(applyInfoDto.getType())) {
                //前置业务和上面一致 见上一个if中的注释
                if ("0".equals(Objects.requireNonNull(activity).getIsTeam())) {
                    quantityNum = activity.getTeamNum();
                }
                //新增人数
                activity.setParticipationNum(activity.getParticipationNum() + quantityNum);
                //这里会使用乐观锁去操作数据库
                int row = activityMapper.updateById(activity);
                Optional.of(row).filter(r -> r == 1).orElseThrow(() -> new RuntimeException("当前在线报名人数较多,请稍后尝试!"));
            } else {
                throw new RuntimeException("抱歉,数据发生异常,请您稍后再试!");
            }

            //7.如果报名成功需要给用户留言箱 传递一条报名成功的消息
            //todo
        });
        return AjaxResult.success("您已经成功报名!");
    }

    @Override
    public List<Object> selectByUser(int type, String userId, int status) {
        List<Object> list = new ArrayList<>();
        if (type == 1) {
            List<ApplyActivityVo> applyActivityVoList = baseMapper.selectApplyActivity(userId, status);
            list.addAll(applyActivityVoList);
        } else {
            List<ApplyContestVo> selectApplyContest = baseMapper.selectApplyContest(userId, status);
            list.addAll(selectApplyContest);
        }
        return list;
    }

    /**
     * 查询报名信息
     *
     * @param id 报名信息主键
     * @return 报名信息
     */
    @Override
    public Apply selectApplyById(String id) {
        return applyMapper.selectApplyById(id);
    }

    /**
     * 查询报名信息列表
     *
     * @param apply 报名信息
     * @return 报名信息
     */
    @Override
    public List<Apply> selectApplyList(Apply apply) {
        return applyMapper.selectApplyList(apply);
    }

    /**
     * 新增报名信息
     *
     * @param apply 报名信息
     * @return 结果
     */
    @Override
    public int insertApply(Apply apply) {
        return applyMapper.insertApply(apply);
    }

    /**
     * 修改报名信息
     *
     * @param apply 报名信息
     * @return 结果
     */
    @Override
    public int updateApply(Apply apply) {
        return applyMapper.updateApply(apply);
    }

    /**
     * 批量删除报名信息
     *
     * @param ids 需要删除的报名信息主键
     * @return 结果
     */
    @Override
    public int deleteApplyByIds(String[] ids) {
        return applyMapper.deleteApplyByIds(ids);
    }

    /**
     * 删除报名信息信息
     *
     * @param id 报名信息主键
     * @return 结果
     */
    @Override
    public int deleteApplyById(String id) {
        return applyMapper.deleteApplyById(id);
    }

    /**
     * 获取参与的竞赛和活动列表
     *
     * @param type   type
     * @param status status
     * @param userId userId
     * @return List<Object></Object>
     */
    @Override
    public List<Object> selectListByUser(String type, String status, String userId) {

        List<String> activityIds = this.applyMapper.selectList(Wrappers.<Apply>lambdaQuery()
                        .eq(Apply::getUserId, userId)
                        .eq(Apply::getType, type)
                        .eq(Apply::getStatus, status))
                .stream().map(Apply::getActivityId).collect(Collectors.toList());

        List<Object> result;

        if ("0".equals(type)) {
            //竞赛
            result = activityIds.stream().map(this.contestMapper::selectById).collect(Collectors.toList());
        } else {
            //活动
            result = activityIds.stream().map(this.activityMapper::selectById).collect(Collectors.toList());
        }

        return result;
    }

    /**
     * 继续报名未提交的报名
     *
     * @param id     id
     * @param type   类型
     * @param userId 用户id
     * @return Apply
     */
    @Override
    public Apply goOnApplyById(String id, Integer type, String userId) {
        //查看当前报名的信息
        return this.applyMapper.selectOne(Wrappers.<Apply>lambdaQuery()
                .eq(Apply::getActivityId, id)
                .eq(Apply::getType, type)
                .eq(Apply::getStatus, 0)
                .eq(Apply::getUserId, userId));
    }

    /**
     * 重新报名接口(这个接口只会删除掉当前报名的这个信息)
     *
     * @param id     id
     * @param type   类型
     * @param userId 用户id
     * @return 是否成功
     */
    @Override
    public String anewApplyById(String id, String type, String userId) {
        //删除当前报名的信息
        int delete = this.applyMapper.delete(Wrappers.<Apply>lambdaQuery()
                .eq(Apply::getActivityId, id)
                .eq(Apply::getType, type)
                .eq(Apply::getStatus, 0)
                .eq(Apply::getUserId, userId));
        return delete == 1 ? "成功" : null;
    }


}
